﻿using DynamicData;
using MQTTnet;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Net;
using System.Reactive;
using System.Reactive.Linq;
using System.Security.Policy;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using MqttAnalyze.Data.Common;
using MqttAnalyze.Data.Models;
using WpfMqttAnalyzeApp.Services;
using MqttAnalyze.Data.Blls;
using System.Collections.ObjectModel;
using System.Security.Cryptography;
using DynamicData.Binding;
using System.Windows.Input;
using System.Windows.Interop;

namespace WpfMqttAnalyzeApp.Model
{
    public class MainMqttVm:ViewModelBase
    {
        #region 属性
        MqttMsgService clientSvc;


        private string _serverAddr;
        private string _sendTopic;
        private string _recvTopic;
        private string _classify;
        private int _panelIndex;
        private int _qosIndex;
        private int _keepNum;
        private bool _isLoading;
        private bool _isMqttConnect;
        private bool? _isRetain;
        private ObservableCollection<MsgTemplate> _templates;
        private ObservableCollection<TopicTreeListener> _topics;
        public List<RecvMessageCategory> RecvMsgList;

        public string CurrentRecvTopic {  get; set; }
        public string CurrentSubClass { get; set; }

        public string MqttAddr { get => _serverAddr; set => this.RaiseAndSetIfChanged(ref _serverAddr, value); }

        public string SendTopic { get => _sendTopic; set => this.RaiseAndSetIfChanged(ref _sendTopic, value); }

        public string RecvTopic { get => _recvTopic; set => this.RaiseAndSetIfChanged(ref _recvTopic, value); }

        public string Classify { get => _classify; set => this.RaiseAndSetIfChanged(ref _classify, value); }

        public int PanelIndex { get => _panelIndex;  set =>   this.RaiseAndSetIfChanged(ref _panelIndex, value);}

        public int QosIndex { get => _qosIndex; set => this.RaiseAndSetIfChanged(ref _qosIndex, value); }

        public int KeepNum { get => _keepNum; set => this.RaiseAndSetIfChanged(ref _keepNum, value); }

        public bool? IsRetain { get => _isRetain; set => this.RaiseAndSetIfChanged(ref _isRetain, value); }
        public bool IsLoading { get => _isLoading; set => this.RaiseAndSetIfChanged(ref _isLoading, value); }
        
        public bool IsMqttConnect { get => _isMqttConnect; set => this.RaiseAndSetIfChanged(ref _isMqttConnect, value); }
        public List<CmbModelItem> QosList { get; set; }

        public ObservableCollection<MsgTemplate> MsgTemplates { get => _templates; set => this.RaiseAndSetIfChanged(ref _templates, value); }

        public ObservableCollection<TopicTreeListener> ListenTopics { get => _topics; set => this.RaiseAndSetIfChanged(ref _topics, value); }
        #endregion

        private IMqttDataBLL bll;

        public MainMqttVm(MqttMsgService cliSvc, UserData data)
        {
            bll = new MqttDataBLL(data.DbPath ?? @".\DataBase\mqtt.db");
            clientSvc = cliSvc;
            PanelIndex = 1;
            KeepNum = 20;
            IsRetain = false;
            CurrentRecvTopic = CurrentSubClass = string.Empty;
            MqttAddr = data.MqttAddr ?? "mqtt://127.0.0.1:1883";
            btnClickResult = new Interaction<OperateResult, Unit>();
            recvMsgResult = new Interaction<OperateResult, Unit>();
            selTopicMsgResult = new Interaction<OperateResult, Unit>();
            _templates = new ObservableCollection<MsgTemplate>();
            _topics = new ObservableCollection<TopicTreeListener>();
            QosList = new List<CmbModelItem>();
            RecvMsgList = new List<RecvMessageCategory>();
            QosList.Add(new CmbModelItem() { OptionValue = 0, DisplayName = "Qos 0" });
            QosList.Add(new CmbModelItem() { OptionValue = 1, DisplayName = "Qos 1" });
            QosList.Add(new CmbModelItem() { OptionValue = 2, DisplayName = "Qos 2" });
            QosIndex = 0;
            RegisterCmd();
        }

        #region 事件交互
        private readonly Interaction<OperateResult, Unit> btnClickResult;
        private readonly Interaction<OperateResult, Unit> recvMsgResult;
        private readonly Interaction<OperateResult, Unit> selTopicMsgResult;
        public Interaction<OperateResult, Unit> ClickRst => this.btnClickResult;

        public Interaction<OperateResult, Unit> RecvMsgRst => this.recvMsgResult;

        public Interaction<OperateResult, Unit> SelectTopicMsgRst => this.selTopicMsgResult;

        public ReactiveCommand<Unit, Unit> CleanContentCmd { get; private set; }
        public ReactiveCommand<Unit, Unit> MqttConnectCmd { get; private set; }

        public ReactiveCommand<Unit, Unit> MqttSubscribeCmd { get; private set; }

        public ReactiveCommand<SendCmdParam, Unit> MqttUnsubscribeCmd { get; private set; }
        public ReactiveCommand<Unit, Unit> ButtonConnectCmd { get; private set; }
        public ReactiveCommand<SendCmdParam, Unit> MqttSendCmd { get; private set; }

        public ReactiveCommand<SaveMsgTemplate, Unit> SaveTemplateCmd { get; private set; }
        public ReactiveCommand<Unit, Unit> MqttDisconnectCmd { get; private set; }

        public ReactiveCommand<Unit, Unit> RefreshTplCmd { get; private set; }
        public ReactiveCommand<Unit, Unit> RefreshTopicCmd { get; private set; }
        public ReactiveCommand<SaveMsgTemplate, Unit> DeleteTplCmd { get; private set; }

      
        private void RegisterCmd()
        {
            ButtonConnectCmd = ReactiveCommand.CreateFromObservable(() => Observable.Defer(() =>
               IsMqttConnect ? MqttDisconnectCmd.Execute() : MqttConnectCmd.Execute()));
            MqttConnectCmd = ReactiveCommand.CreateFromTask(async () =>
            {
                var rst = new OperateResult();
                rst.Operation = EOperationType.Connect;
                try
                {
                    IsLoading = true;
                    string mqttAddr = _serverAddr + "/";
                    string urlFormat = @"^(?<proto>\w+)://(?<addr>[^/]+)/";
                    var reg = new Regex(urlFormat, RegexOptions.None);
                    var m = reg.Match(mqttAddr);
                    if (!m.Success)
                    {
                        rst.RtnCode = EErrorCode.InputError;
                        goto TOEND;
                    }
                    int mqttPort = 1883;
                    string IpAddr = m.Groups["addr"].Value;
                    if (IpAddr.Contains(":"))
                    {
                        mqttPort = ConvertHelper.ToInt32(IpAddr.Substring(IpAddr.IndexOf(':') + 1));
                        IpAddr = IpAddr.Substring(0, IpAddr.IndexOf(":"));
                    }
                    string clientId = "WpfClient_" + Guid.NewGuid().ToString("N");
                    string url = @"^(?<proto>\w+)://(?<ip>[^/]+)?(?<port>:\d+)?/";
                    rst.RtnCode = EErrorCode.CommunicateFail;
                    clientSvc.Init(IpAddr, mqttPort, clientId);
                    var isConnect=await clientSvc.Connect(MqttMsgReceived);
                    if (isConnect)
                    {
                        rst.RtnCode = EErrorCode.Success;
                        IsMqttConnect = clientSvc.IsConnect();
                    }
                }
                catch (Exception ex)
                {
                    rst.RtnCode = EErrorCode.Exception;
                }
                TOEND:
                await this.btnClickResult.Handle(rst);
            });

            MqttDisconnectCmd = ReactiveCommand.CreateFromTask(async () =>
            {
                var rst = new OperateResult();
                rst.Operation = EOperationType.Disconnect;
                try
                {
                    IsLoading = true;
                    await clientSvc.Disconnect();
                    IsMqttConnect = clientSvc.IsConnect();
                    rst.RtnCode = EErrorCode.Success;
                }
                catch (Exception ex)
                {
                    rst.RtnCode = EErrorCode.Exception;
                }
                await this.btnClickResult.Handle(rst);
            });

            #region 发送
            CleanContentCmd = ReactiveCommand.CreateFromTask(async () =>
            {
                var rst = new OperateResult();
                rst.Operation = EOperationType.ClearContent;
                rst.RtnCode = EErrorCode.Success;
                if (PanelIndex == 2)
                {
                    var item = RecvMsgList.FirstOrDefault(t => t.Topic == CurrentRecvTopic);
                    if (item != null)
                    {
                        if (string.IsNullOrEmpty(CurrentSubClass))
                        {
                            item.MsgList.Clear();
                        }
                        else
                        {
                            item.MsgList.RemoveAll(t => t.SubClass == CurrentSubClass);
                        }
                    }
                }
                await this.btnClickResult.Handle(rst);
            });


            MqttSendCmd = ReactiveCommand.CreateFromTask(async (SendCmdParam req) =>
            {
                if (req == null)
                {
                    return;
                }
                var rst = new OperateResult();
                rst.Operation = EOperationType.SendMsg;
                try
                {

                    if (string.IsNullOrEmpty(req.Message) || string.IsNullOrEmpty(req.Topic))
                    {
                        rst.RtnCode = EErrorCode.InputError;
                        goto TOEND;
                    }
                    var sendRst = clientSvc.SendMessage(req.Topic, req.Message, req.Qos, req.IsRetain);
                    if (!sendRst)
                    {
                        rst.RtnCode = EErrorCode.CommunicateFail;
                    }
                }
                catch (Exception ex)
                {
                    rst.RtnCode = EErrorCode.Exception;
                }
                TOEND:
                await this.btnClickResult.Handle(rst);
            });

            SaveTemplateCmd = ReactiveCommand.CreateFromTask(async (SaveMsgTemplate req) =>
            {
                var rst = new OperateResult();
                rst.Operation = EOperationType.SaveTemplate;
                try
                {
                    if (string.IsNullOrEmpty(req.Message) || string.IsNullOrEmpty(req.Topic))
                    {
                        rst.RtnCode = EErrorCode.InputError;
                        goto TOEND;
                    }
                    var tpl = new MqttMessageTemplateTb();
                    tpl.IsRetain = req.IsRetain;
                    tpl.Qos = req.Qos;
                    tpl.Message = req.Message;
                    tpl.Topic = req.Topic;
                    tpl.TemplateName = req.TemplateName;
                    var dbRst = bll.SaveMsgTemplate(tpl);
                    if (dbRst.ErrorCode != SqlErrorCode.KSuccess)
                    {
                        rst.RtnCode = EErrorCode.InputError;
                    }
                    else
                    {
                        rst.RtnCode = EErrorCode.Success;
                    }
                }
                catch (Exception ex)
                {
                    rst.RtnCode = EErrorCode.Exception;
                }
                TOEND:
                await this.btnClickResult.Handle(rst);
            });

            RefreshTplCmd = ReactiveCommand.CreateFromTask(async () =>
            {
                var rst = new OperateResult();
                rst.Operation = EOperationType.GetTemplates;
                try
                {
                    var dbRst = bll.GetMsgTemplateList();
                    if (dbRst.ErrorCode != SqlErrorCode.KSuccess)
                    {
                        rst.RtnCode = EErrorCode.InputError;
                    }
                    else
                    {
                        rst.RtnCode = EErrorCode.Success;
                        var dataList = dbRst.Data as List<MqttMessageTemplateTb>;
                        MsgTemplates.Clear();
                        foreach (var item in dataList)
                        {
                            var newItem = new MsgTemplate();
                            newItem.Message = item.Message;
                            newItem.Topic = item.Topic;
                            newItem.TemplateId = item.TemplateId;
                            newItem.TemplateName = item.TemplateName;
                            newItem.IsRetain = item.IsRetain;
                            newItem.Qos = item.Qos;
                            MsgTemplates.Add(newItem);
                        }
                    }
                }
                catch (Exception ex)
                {
                    rst.RtnCode = EErrorCode.Exception;
                }
                TOEND:
                await this.btnClickResult.Handle(rst);
            });

            RefreshTopicCmd = ReactiveCommand.CreateFromTask(async () =>
            {
                var rst = new OperateResult();
                rst.Operation = EOperationType.GetCategories;
                try
                {
                    var queryRst = bll.GetMsgCategoryList();
                    if (queryRst.ErrorCode == EErrorCode.Success)
                    {
                        RecvMsgList = queryRst.Data as List<RecvMessageCategory>;
                        ListenTopics.Clear();
                        foreach (var topicItem in RecvMsgList)
                        {
                            var newItem = new TopicTreeListener();
                            newItem.NodeName = topicItem.Topic;
                            newItem.Topic = topicItem.Topic;
                            newItem.IsExpanded = true;
                            newItem.IsLeaf = false;
                            newItem.TotalNum = topicItem.MsgList.Count;
                            newItem.SubClass = new ObservableCollection<TopicTreeListener>();
                            var subList = topicItem.MsgList.Select(t => t.SubClass).Distinct().ToList();
                            if (subList.Count < 1 || (subList.Count == 1 && string.IsNullOrEmpty(subList[0])))
                            {
                                string nodeName = "Default";
                                if (!string.IsNullOrEmpty(topicItem.Topic))
                                {
                                    nodeName = string.Format("({0}[{1}])", nodeName, topicItem.Classify);
                                }
                                else
                                {
                                    nodeName = "(Default)";
                                }
                                newItem.SubClass.Add(new TopicTreeListener() { Topic= topicItem.Topic, IsLeaf = true, IsExpanded = false, NodeName = nodeName, TotalNum = topicItem.MsgList.Count });
                            }
                            else
                            {
                                foreach (var nodeName in subList)
                                {
                                    int totalNum = topicItem.MsgList.Count(t => t.SubClass == nodeName);
                                    newItem.SubClass.Add(new TopicTreeListener() { Topic = topicItem.Topic, IsLeaf = true, IsExpanded = false, NodeName = nodeName, TotalNum = totalNum });
                                }
                            }
                            ListenTopics.Add(newItem);
                            clientSvc.SubscribeTopics(topicItem.Topic);
                        }
                    }
                }
                catch (Exception ex)
                {
                    rst.RtnCode = EErrorCode.Exception;
                }
                TOEND:
                await this.btnClickResult.Handle(rst);
            });

            DeleteTplCmd = ReactiveCommand.CreateFromTask(async (SaveMsgTemplate req) =>
            {
                var rst = new OperateResult();
                rst.Operation = EOperationType.RemoveTemplate;
                try
                {
                    var dbRst = bll.RemoveMsgTemplate(req.TemplateId);
                    if (dbRst.ErrorCode != SqlErrorCode.KSuccess)
                    {
                        rst.RtnCode = EErrorCode.InputError;
                    }
                    else
                    {
                        rst.RtnCode = EErrorCode.Success;
                    }
                }
                catch (Exception ex)
                {
                    rst.RtnCode = EErrorCode.Exception;
                }
                TOEND:
                await this.btnClickResult.Handle(rst);
            });
            #endregion

            MqttSubscribeCmd = ReactiveCommand.CreateFromTask(async () =>
            {
                var rst = new OperateResult();
                rst.RtnCode = EErrorCode.Success;
                try
                {
                    IsLoading = true;
                    string topic = RecvTopic;
                    string nodeName = "Default";
                    if(!string.IsNullOrEmpty(Classify))
                    {
                        nodeName = string.Format("({0}[{1}])", nodeName, Classify);
                    }
                    else
                    {
                        nodeName = "(Default)";
                    }
                    if (!_topics.Any(t => t.Topic == topic))
                    {                        
                        var newTopic = new RecvTopicCategory();
                        newTopic.Classify = Classify;
                        newTopic.KeepNum = KeepNum;
                        newTopic.Topic = RecvTopic;
                        var newRst=bll.SaveMsgCategory(newTopic);
                        if(newRst.ErrorCode!=EErrorCode.Success)
                        {
                            rst.RtnCode =newRst.ErrorCode;
                            goto TOEND;
                        }
                        string catId = newRst.Data as string;
                        var newCat = new RecvMessageCategory();
                        newCat.CategoryId = catId;
                        newCat.Classify=Classify;
                        newCat.KeepNum = KeepNum;
                        newCat.Topic = RecvTopic;
                        newCat.MsgList = new List<MqttMessage>();
                        RecvMsgList.Add(newCat);
                        var newItem = new TopicTreeListener();
                        newItem.NodeName = topic;
                        newItem.Topic = topic;
                        newItem.IsExpanded = true;
                        newItem.IsLeaf = false;
                        newItem.SubClass = new ObservableCollection<TopicTreeListener>() { new TopicTreeListener() {Topic=topic, IsLeaf = false, IsExpanded = false, NodeName = nodeName, TotalNum = 0 } };
                        ListenTopics.Add(newItem);

                        clientSvc.SubscribeTopics(topic);

                        RecvTopic = string.Empty;
                        Classify = string.Empty;
                        KeepNum = 20;
                    }
                    else
                    {
                        rst.RtnCode= EErrorCode.Duplicate;
                    }
                }
                catch (Exception ex)
                {
                }
                TOEND:
                await this.btnClickResult.Handle(rst);
            });

            MqttUnsubscribeCmd = ReactiveCommand.CreateFromTask(async (SendCmdParam req) =>
            {
                var rst = new OperateResult();
                rst.Operation = EOperationType.Unsubscribe;
                try
                {
                    var recvItem=RecvMsgList.FirstOrDefault(t=>t.Topic==req.Topic);
                    if (recvItem != null)
                    {
                        var removeRst = bll.RemoveMsgCategory(recvItem.CategoryId);
                        if (removeRst.ErrorCode == EErrorCode.Success)
                        {
                            var item = ListenTopics.FirstOrDefault(t => t.Topic == req.Topic);
                            ListenTopics.Remove(item);
                            clientSvc.UnscribeTopics(req.Topic);
                            RecvMsgList.Remove(recvItem);
                            rst.RtnData = req.Topic;
                            rst.RtnCode = EErrorCode.Success;
                        }
                        else
                        {
                            rst.RtnCode = removeRst.ErrorCode;
                        }
                    }
                }
                catch (Exception ex)
                {
                    rst.RtnCode = EErrorCode.Exception;
                }
                TOEND:
                await this.btnClickResult.Handle(rst);
            });

        }
        #endregion

        public void ShowTemplateData(MsgTemplate data)
        {
            SendTopic = data.Topic;
            IsRetain=data.IsRetain;
            QosIndex = data.Qos;
        }
        public void ShowTopicCategory(string topic)
        {
            var item = RecvMsgList.FirstOrDefault(t => t.Topic == topic);
            if (item != null)
            {
                RecvTopic = item.Topic;
                Classify = item.Classify;
                KeepNum = item.KeepNum;
            }
        }

        public async void SetCurrentCategory(string topic, string subClass)
        {
            CurrentRecvTopic = topic;
            CurrentSubClass = subClass;
            var item = RecvMsgList.FirstOrDefault(t => t.Topic == topic);
            if (item != null)
            {
                var rst = new OperateResult();
                rst.Operation = EOperationType.OpenCategory;
                rst.RtnCode = EErrorCode.Success;
                if (string.IsNullOrEmpty(subClass))
                {
                    rst.RtnData = item.MsgList;
                }
                else
                {
                    rst.RtnData = item.MsgList.Where(t=>t.SubClass== subClass).ToList();
                }
                await this.SelectTopicMsgRst.Handle(rst);
            }
        }

        public void UpdateTopicCount()
        {
            foreach (var topicItem in ListenTopics)
            {
                var topicMsgList= RecvMsgList.FirstOrDefault(t=>t.Topic== topicItem.Topic);
                if (topicMsgList != null)
                {
                    topicItem.TotalNum = topicMsgList.MsgList.Count;
                    foreach (var subClass in topicItem.SubClass)
                    {
                        subClass.TotalNum = topicMsgList.MsgList.Count(t => t.SubClass == subClass.NodeName);
                    }
                }
                else
                {
                    topicItem.TotalNum = 0;
                    foreach (var subClass in topicItem.SubClass)
                    {
                        subClass.TotalNum = 0;
                    }
                }
            }
        }

        public void UpdateRecvMsgCount(RecvMessage msg)
        {
            var topicListener = ListenTopics.FirstOrDefault(t => t.Topic == msg.Topic);
            var item = RecvMsgList.FirstOrDefault(t => t.Topic == msg.Topic);
            if (topicListener != null)
            {
                topicListener.TotalNum = item.MsgList.Count;
                if (!string.IsNullOrEmpty(msg.SubClass))
                {
                    if (!topicListener.SubClass.Any(t => t.NodeName == msg.SubClass))
                    {
                        if (topicListener.SubClass.Count == 1 && topicListener.SubClass[0].NodeName.StartsWith("(Default"))
                        {
                            topicListener.SubClass.Clear();
                        }
                        topicListener.SubClass.Add(new TopicTreeListener() { Topic = msg.Topic, IsLeaf = true, IsExpanded = false, NodeName = msg.SubClass, TotalNum = 1 });
                    }
                }
                foreach (var subClass in topicListener.SubClass)
                {
                    subClass.TotalNum = item.MsgList.Count(m => m.SubClass == subClass.NodeName);
                }
            }
        }

        /// <summary>
        /// mqtt接收的消息处理
        /// </summary>
        /// <param name="msg"></param>
        private async void MqttMsgReceived(MqttApplicationMessage msg)
        {
            try
            {
                var rst = new OperateResult();
                rst.Operation = EOperationType.ReceiveMsg;
                rst.RtnCode = EErrorCode.Success;
                string nowTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:sszzz");

                var item= RecvMsgList.FirstOrDefault(t => t.Topic == msg.Topic);
                if (item != null)
                {
                    var recvMsg = new MqttRecvMessage();
                    recvMsg.CategoryId = item.CategoryId;
                    recvMsg.Topic = msg.Topic;
                    recvMsg.Content = Encoding.UTF8.GetString(msg.PayloadSegment.ToArray());
                    recvMsg.RecvTime = nowTime;
                    recvMsg.SubClass = GetJsonKeyValue(recvMsg.Content, item.Classify);
                    bll.SaveRecvMsg(recvMsg);

                    var returnMsg= new RecvMessage() { Topic = msg.Topic, SubClass = recvMsg.SubClass, Content = recvMsg.Content, RecvTime = nowTime };
                    if (item.KeepNum > item.MsgList.Count)
                    {
                        item.MsgList.Add(returnMsg);
                    }
                    else
                    {
                        item.MsgList.RemoveAt(0);
                        item.MsgList.Add(returnMsg);
                    }
                    if (!item.SubClass.Contains(recvMsg.SubClass))
                    {
                        item.SubClass.Add(recvMsg.SubClass);
                    }
                    rst.RtnData = returnMsg;
                }
                else
                {
                    rst.RtnCode = EErrorCode.DataNotExists;
                }
                await this.recvMsgResult.Handle(rst);
            }
            catch (Exception ex)
            {

            }
        }

        private string GetJsonKeyValue(string json,string key)
        {
            string jsonValue=string.Empty;
            string pattern = $"\"{key}\"\\s*:\\s*(\"(.*?)\"|true|false|\\d+)";
            var match = Regex.Match(json, pattern, RegexOptions.IgnoreCase);
            if (match.Success)
            {
                jsonValue = match.Groups[2].Success ? match.Groups[2].Value : match.Groups[1].Value;
            }
            return jsonValue;
        }
    }
}
